/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_lightCache.cpp - light cache data
//


#include "r_local.h"


/*
 ==================
 
 ==================
*/
static bool R_CullLightSurface (lightCache_t *lightCache, surface_t *surface, material_t *material, const glqVec3 &lightOrigin, const glqVec3 &lightDirection, int cullBits){

	// Cull face

	// Cull bounds
	if (cullBits != CULL_IN){
		if (R_LightCullBounds(&lightCache->data, surface->bounds, cullBits) == CULL_OUT)
			return true;
	}

	// Cull triangles

	return true;
}

/*
 ==================
 R_CacheLightSurfaces
 ==================
*/
static void R_CacheLightSurfaces (scene_t *scene, lightCache_t *lightCache, surface_t **surfaceList, int maxSurfaces, node_t *node, int cullBits){

	lightNode_t	*lightNode;
	leaf_t		*leaf;
	surface_t	*surface, **mark;
	bool		addShadow, addInteraction;
	int			i, count = 0;

	while (1){
		// Check for solid content
		if (node->contents == CONTENT_SOLID)
			return;

		// Cull
		if (cullBits != CULL_IN){
			cullBits = R_LightCullBounds(&lightCache->data, node->bounds, cullBits);

			if (cullBits == CULL_OUT)
				return;
		}

		if (node->contents < 0)
			break;		// Leaf node

		// Recurse down the children, front side first
		R_CacheLightSurfaces(scene, lightCache, surfaceList, maxSurfaces, node->children[0], cullBits);

		// Tail recurse
		node = node->children[1];
	}

	// Leaf node
	leaf = (leaf_t *)node;

	if (!leaf->numMarkSurfaces)
		return;

	// Add all the surfaces
	for (i = 0, mark = leaf->firstMarkSurface; i < leaf->numMarkSurfaces; i++, mark++){
		surface = *mark;

		if (surface->texInfo->material->spectrum != lightCache->data.material->spectrum)
			continue;		// Not illuminated by this light

		// Determine if we should add the shadow
		if (!(lightCache->data.material->flags & MF_NOSHADOWS) && !(surface->texInfo->material->flags & MF_NOSHADOWS))
			addShadow = true;
		else
			addShadow = false;

		// Determine if we should add the interaction
		switch (lightCache->data.material->lightType){
		case LT_GENERIC:
			addInteraction = !(surface->texInfo->material->flags & MF_NOINTERACTIONS);
			break;
		case LT_AMBIENT:
			addInteraction = !(surface->texInfo->material->flags & MF_NOAMBIENT);
			break;
		case LT_BLEND:
			addInteraction = !(surface->texInfo->material->flags & MF_NOBLEND);
			break;
		case LT_FOG:
			addInteraction = !(surface->texInfo->material->flags & MF_NOFOG);
			break;
		}

		// Check if there's nothing to be added
		if (!addShadow && !addInteraction)
			continue;

		// Cull the surface
		if (R_CullLightSurface(lightCache, surface, surface->texInfo->material, lightCache->data.origin, lightCache->data.direction, cullBits))
			continue;

		// Add the surface
		if (count == maxSurfaces)
			break;

		surfaceList[count++] = surface;
	}

	// Add a new node if needed
	if (!count)
		return;

	scene->size += sizeof(lightNode_t) + (count * sizeof(surface_t *));

	lightNode = (lightNode_t *)Mem_Alloc(sizeof(lightNode_t), TAG_RENDERER);

	lightNode->leaf = node;
	lightNode->numSurfaces = count;
	lightNode->surfaces = (surface_t **)Mem_DupData(surfaceList, count * sizeof(surface_t *), TAG_RENDERER);

	// Link
	lightNode->next = lightCache->data.nodes;
	lightCache->data.nodes = lightNode;
}

/*
 ==================
 R_PrecacheLights

 TODO: also precache entity surfaces?, we only precache world surfaces at the moment
 ==================
*/
void R_PrecacheLights (scene_t *scene, int numRenderLights, const renderLight_t *renderLights){

	const renderLight_t	*renderLight;
	surface_t			*surfaceList[MAX_DRAW_SURFACES];
	lightCache_t		*lightCache;
	lightNode_t			*lightNode;
	int					totalLights = 0;
	int					totalNodes = 0, totalSurfaces = 0, totalRefs = 0, totalBytes = 0;
	int					time;
	int					i, j;

	if (scene->inUse)
		Com_Error(false, "R_PrecacheLights: scene is currently in use");

	if (!scene->primary)
		return;

	if (!rg.world)
		Com_Error(false, "R_PrecacheLights: NULL world");

	time = Sys_Milliseconds();

	for (i = 0, renderLight = renderLights; i < numRenderLights; i++, renderLight++){
		if (renderLight->lightNum < 0 || renderLight->lightNum >= MAX_ENTITIES)
			continue;		// Should never happen

		if (scene->lightCache[renderLight->lightNum])
			continue;		// Already precached

		// Allocate a light cache
		scene->lightCache[renderLight->lightNum] = lightCache = (lightCache_t *)Mem_ClearedAlloc(sizeof(lightCache_t), TAG_RENDERER);
		scene->size += sizeof(lightCache_t);

		// Copy the render light
		lightCache->l = *renderLight;

		// Set up the light data
		R_SetupLightData(scene, &lightCache->l, &lightCache->data, true);

		// Precache lists of nodes and surfaces
		lightCache->data.precached = true;

		R_CacheLightSurfaces(scene, lightCache, surfaceList, MAX_DRAW_SURFACES, rg.world->nodes, LIGHT_PLANEBITS);

		// Update statistics
		rg.checkCount++;

		totalLights++;

		for (lightNode = lightCache->data.nodes; lightNode; lightNode = lightNode->next){
			totalNodes++;

			for (j = 0; j < lightNode->numSurfaces; j++){
				if (lightNode->surfaces[j]->checkCount == rg.checkCount)
					continue;
				lightNode->surfaces[j]->checkCount = rg.checkCount;

				totalSurfaces++;
			}

			totalRefs += lightNode->numSurfaces;
			totalBytes += sizeof(lightNode_t) + (lightNode->numSurfaces * sizeof(surface_t *));
		}
	}

	// Print statistics
	Com_Printf("%i lights precached in %i msec\n", totalLights, Sys_Milliseconds() - time);
	Com_Printf("%i light surfaces (%i refs) in %i nodes totalling %i KB\n", totalSurfaces, totalRefs, totalNodes, SIZE_KB(totalBytes));
}


// ============================================================================


/*
 ==================
 R_PrecachedLightData
 ==================
*/
bool R_PrecachedLightData (scene_t *scene, const renderLight_t *renderLight, lightData_t *lightData){

	lightCache_t	*lightCache;
	int				diff;

	if (renderLight->lightNum < 0 || renderLight->lightNum >= MAX_ENTITIES)
		return false;	// Should never happen

	lightCache = scene->lightCache[renderLight->lightNum];

	if (!lightCache || !lightCache->data.precached)
		return false;	// Not precached

	// If the light has been moved, rotated, or resized since the map was
	// loaded, or if the type or material has been changed, then the precached
	// light data is no longer valid and the light must be treated as a fully
	// dynamic light
	diff = 0;

	diff |= (lightCache->l.type != renderLight->type);
	diff |= (lightCache->l.lightNum != renderLight->lightNum);
	diff |= (lightCache->l.origin != renderLight->origin);
	diff |= (lightCache->l.center != renderLight->center);
	diff |= (lightCache->l.axis != renderLight->axis);
	diff |= (lightCache->l.radius != renderLight->radius);
	diff |= (lightCache->l.xMin != renderLight->xMin);
	diff |= (lightCache->l.xMax != renderLight->xMax);
	diff |= (lightCache->l.yMin != renderLight->yMin);
	diff |= (lightCache->l.yMax != renderLight->yMax);
	diff |= (lightCache->l.zNear != renderLight->zNear);
	diff |= (lightCache->l.zFar != renderLight->zFar);
	diff |= (lightCache->l.material != renderLight->material);

	if (diff)
		return false;

	// The render light hasn't changed, so copy the precached light data
	Mem_Copy(lightData, &lightCache->data, sizeof(lightData_t));

	return true;
}